package il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.calibration.stablecalibrate;

import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.Recognizer;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.TileImageMap;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.calibration.common.CalibrationCompositeData;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.calibration.common.CalibrationConstants;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.common.rectifier.BoardRectifier;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.common.squareiter.SquareCropper;
import il.ac.tau.cvws.scrabblesidekick.server.util.cv.CvUtil;
import il.ac.tau.cvws.scrabblesidekick.server.util.debug.DebugUtil;
import il.ac.tau.cvws.scrabblesidekick.server.util.debug.DebugUtil.DebugTypes;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Board;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Tile;

import com.googlecode.javacv.cpp.opencv_core.CvRect;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

/* Calibrates the application using an image known to contain a Scrabble board
 * close to a given position.
 */
public class CalibrationRecognizer implements Recognizer, SquareCropper.Callback
{
	private final TileImageMap tileImageMap;
	
	private final int screenWidth;
	private final int screenHeight;
	
	public CalibrationRecognizer(int screenWidth, int screenHeight)
	{
		this.tileImageMap = new TileImageMap();
		
		this.screenWidth = screenWidth;
		this.screenHeight = screenHeight;
	}
	
	/* Attempts to detect the board with the given image, and crop the game tiles
	 * if the detection is successful.
	 */
	@Override
	public CalibrationCompositeData recognize(IplImage sceneImage)
	{
		BoardRectifier rectifier = new BoardRectifier();
		IplImage boardImage = rectifier.rectify(sceneImage, screenWidth, screenHeight);
		
		if (boardImage == null)
		{
			return null;
		}
		
		DebugUtil.saveImage(boardImage, DebugTypes.CALIBRATION, "rectified", true);	
		
		SquareCropper cropper = new SquareCropper();
		cropper.crop(boardImage, this);
		
		return new CalibrationCompositeData(tileImageMap, null);
	}
	
	/* Given the image of given square on the board, attempt to find the letter within in
	 * and crop it, to store in the resulting tile-image map.
	 */
	@Override
	public void handleSquare(SquareCropper cropper, IplImage squareImage, int row, int col)
	{
		int logicalCol = (Board.SQUARES_NUM - 2) - col;
		int logicalRow = (Board.SQUARES_NUM - 2) - row;
		
		if ((logicalRow < 0) || (logicalCol < 0) || (logicalCol >= CalibrationConstants.TILES_IN_ROW))
		{
			return;
		}
		
		int tileId = logicalRow * CalibrationConstants.TILES_IN_ROW + logicalCol;
		
		if (tileId >= CalibrationConstants.LETTER_COUNT)
		{
			return;
		}
		
		int minFrameWidth  = (int)(CalibrationConstants.TILE_FRAME_MIN_RATIO * squareImage.width());
		int maxFrameWidth  = (int)(CalibrationConstants.TILE_FRAME_MAX_RATIO * squareImage.width());
		int minFrameHeight = (int)(CalibrationConstants.TILE_FRAME_MIN_RATIO * squareImage.height());
		int maxFrameHeight = (int)(CalibrationConstants.TILE_FRAME_MAX_RATIO * squareImage.height());
		
		DebugUtil.saveImage(squareImage, DebugTypes.CALIBRATION, "raw_tile_" + tileId, true);
		
		/* Try to find a clean (white) frame around the potential letter. If found,
		 * crop by it ond store the clean image in the map.
		 */
		CvRect tileCleanRect = CvUtil.findCleanImageFrame(squareImage,
				minFrameWidth, maxFrameWidth, minFrameHeight, maxFrameHeight,
				CalibrationConstants.TILE_FRAME_THICKNESS);
		
		IplImage tileCleanImage = null;
		
		if (tileCleanRect == null)
		{
			tileCleanImage = squareImage;
			
			System.out.println("WARNING: there's something wrong with this tile");
		}
		else
		{
			tileCleanImage = CvUtil.cropImage(squareImage, tileCleanRect);
			
			DebugUtil.saveImage(tileCleanImage, DebugTypes.CALIBRATION, "clean_tile_" + tileId, true);
		}
		
		tileImageMap.put(new Tile(tileId + 1, 1), tileCleanImage); // char = ' ' --> tileId = 0; 
																   // therefore char = 'a' --> tildId = 1 ... 
	}
}
